/*
 * Conditions Of Use 
 * 
 * This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 Untied States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 * 
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof, including but
 * not limited to the correctness, accuracy, reliability or usefulness of
 * the software.
 * 
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement
 *  
 * .
 * 
 */
/*******************************************************************************
 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD).       *
 ******************************************************************************/
package gov.nist.javax.sip.stack;

import gov.nist.javax.sip.message.*;
import gov.nist.javax.sip.address.*;
import gov.nist.javax.sip.header.*;
import gov.nist.javax.sip.*;
import gov.nist.core.*;
import gov.nist.core.net.AddressResolver;

import javax.sip.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

import javax.sip.header.RouteHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.*;
import javax.sip.address.*;

/*
 * Bug reported by Will Scullin -- maddr was being ignored when routing
 * requests. Bug reported by Antonis Karydas - the RequestURI can be a non-sip
 * URI Jiang He - use address in route header. Significant changes to conform to
 * RFC 3261 made by Jeroen van Bemmel. Hagai Sela contributed a bug fix to the
 * strict route post processing code.
 * 
 */

/**
 * This is the default router. When the implementation wants to forward a
 * request and had run out of othe options, then it calls this method to figure
 * out where to send the request. The default router implements a simple
 * "default routing algorithm" which just forwards to the configured proxy
 * address.
 * 
 * <p>
 * When <code>javax.sip.USE_ROUTER_FOR_ALL_URIS</code> is set to
 * <code>false</code>, the next hop is determined according to the following
 * algorithm:
 * <ul>
 * <li> If the request contains one or more Route headers, use the URI of the
 * topmost Route header as next hop, possibly modifying the request in the
 * process if the topmost Route header contains no lr parameter(*)
 * <li> Else, if the property <code>javax.sip.OUTBOUND_PROXY</code> is set,
 * use its value as the next hop
 * <li> Otherwise, use the request URI as next hop. If the request URI is not a
 * SIP URI, call {@link javax.sip.address.Router#getNextHop(Request)} provided
 * by the application.
 * </ul>
 * 
 * <p>
 * (*)Note that in case the topmost Route header contains no 'lr' parameter
 * (which means the next hop is a strict router), the implementation will
 * perform 'Route Information Postprocessing' as described in RFC3261 section
 * 16.6 step 6 (also known as "Route header popping"). That is, the following
 * modifications will be made to the request:
 * <ol>
 * <li>The implementation places the Request-URI into the Route header field as
 * the last value.
 * <li>The implementation then places the first Route header field value into
 * the Request-URI and removes that value from the Route header field.
 * </ol>
 * Subsequently, the request URI will be used as next hop target
 * 
 * 
 * @version 1.2 $Revision: 1.13 $ $Date: 2008/11/03 14:12:08 $
 * 
 * @author M. Ranganathan <br/>
 * 
 */
public class DefaultRouter implements Router {

	private SipStackImpl sipStack;

	private Hop defaultRoute;

	private DefaultRouter() {

	}

	/**
	 * Constructor.
	 */
	public DefaultRouter(SipStack sipStack, String defaultRoute) {
		this.sipStack = (SipStackImpl) sipStack;
		if (defaultRoute != null) {
			try {
				this.defaultRoute = (Hop) this.sipStack.getAddressResolver()
						.resolveAddress((Hop) (new HopImpl(defaultRoute)));
			} catch (IllegalArgumentException ex) {
				// The outbound proxy is optional. If specified it should be host:port/transport.
				((SIPTransactionStack) sipStack)
						.getLogWriter()
						.logError(
								"Invalid default route specification - need host:port/transport");
				throw ex;
			}
		}
	}

	/**
	 * Return addresses for default proxy to forward the request to. The list is
	 * organized in the following priority. If the requestURI refers directly to
	 * a host, the host and port information are extracted from it and made the
	 * next hop on the list. If the default route has been specified, then it is
	 * used to construct the next element of the list. <code>
	 * RouteHeader firstRoute = (RouteHeader) req.getHeader( RouteHeader.NAME );
	 * if (firstRoute!=null) {
	 *   URI uri = firstRoute.getAddress().getURI();
	 *    if (uri.isSIPUri()) {
	 *       SipURI nextHop = (SipURI) uri;
	 *       if ( nextHop.hasLrParam() ) {
	 *           // OK, use it
	 *       } else {
	 *           nextHop = fixStrictRouting( req );        <--- Here, make the modifications as per RFC3261
	 *       }
	 *   } else {
	 *       // error: non-SIP URI not allowed in Route headers
	 *       throw new SipException( "Request has Route header with non-SIP URI" );
	 *   }
	 * } else if (outboundProxy!=null) {
	 *   // use outbound proxy for nextHop
	 * } else if ( req.getRequestURI().isSipURI() ) {
	 *   // use request URI for nextHop
	 * } 
	 *
	 * </code>
	 * 
	 * @param request
	 *            is the sip request to route.
	 * 
	 */
	public Hop getNextHop(Request request) throws SipException {

		SIPRequest sipRequest = (SIPRequest) request;

		RequestLine requestLine = sipRequest.getRequestLine();
		if (requestLine == null) {
			return defaultRoute;
		}
		javax.sip.address.URI requestURI = requestLine.getUri();
		if (requestURI == null)
			throw new IllegalArgumentException("Bad message: Null requestURI");

		RouteList routes = sipRequest.getRouteHeaders();

		/*
		 * In case the topmost Route header contains no 'lr' parameter (which
		 * means the next hop is a strict router), the implementation will
		 * perform 'Route Information Postprocessing' as described in RFC3261
		 * section 16.6 step 6 (also known as "Route header popping"). That is,
		 * the following modifications will be made to the request:
		 * 
		 * The implementation places the Request-URI into the Route header field
		 * as the last value.
		 * 
		 * The implementation then places the first Route header field value
		 * into the Request-URI and removes that value from the Route header
		 * field.
		 * 
		 * Subsequently, the request URI will be used as next hop target
		 */

		if (routes != null) {

			// to send the request through a specified hop the application is
			// supposed to prepend the appropriate Route header which.
			Route route = (Route) routes.getFirst();
			URI uri = route.getAddress().getURI();
			if (uri.isSipURI()) {
				SipURI sipUri = (SipURI) uri;
				if (!sipUri.hasLrParam()) {

					fixStrictRouting(sipRequest);
					if (sipStack.isLoggingEnabled())
						sipStack.logWriter
								.logDebug("Route post processing fixed strict routing");
				}

				Hop hop = createHop(sipUri,request);
				if (sipStack.isLoggingEnabled())
					sipStack.logWriter
							.logDebug("NextHop based on Route:" + hop);
				return hop;
			} else {
				throw new SipException("First Route not a SIP URI");
			}

		} else if (requestURI.isSipURI()
				&& ((SipURI) requestURI).getMAddrParam() != null) {
			Hop hop = createHop((SipURI) requestURI,request);
			if (sipStack.isLoggingEnabled())
				sipStack.logWriter
						.logDebug("Using request URI maddr to route the request = "
								+ hop.toString());
			((SipURI) requestURI).removeParameter("maddr");
            
            
			return hop;

		} else if (defaultRoute != null) {
			if (sipStack.isLoggingEnabled())
				sipStack.logWriter
						.logDebug("Using outbound proxy to route the request = "
								+ defaultRoute.toString());
			return defaultRoute;
		} else if (requestURI.isSipURI()) {
			Hop hop = createHop((SipURI) requestURI,request);
			if (hop != null && sipStack.isLoggingEnabled())
				sipStack.logWriter.logDebug("Used request-URI for nextHop = "
						+ hop.toString());
			else if (sipStack.isLoggingEnabled()) {
				sipStack.logWriter
						.logDebug("returning null hop -- loop detected");
			}
			return hop;

		} else {
			// The internal router should never be consulted for non-sip URIs.
			InternalErrorHandler.handleException("Unexpected non-sip URI",
					this.sipStack.logWriter);
			return null;
		}

	}

	/**
	 * Performs strict router fix according to RFC3261 section 16.6 step 6
	 * 
	 * pre: top route header in request has no 'lr' parameter in URI post:
	 * request-URI added as last route header, new req-URI = top-route-URI
	 */
	public void fixStrictRouting(SIPRequest req) {

		RouteList routes = req.getRouteHeaders();
		Route first = (Route) routes.getFirst();
		SipUri firstUri = (SipUri) first.getAddress().getURI();
		routes.removeFirst();

		// Add request-URI as last Route entry
		AddressImpl addr = new AddressImpl();
		addr.setAddess(req.getRequestURI()); // don't clone it
		Route route = new Route(addr);

		routes.add(route); // as last one
		req.setRequestURI(firstUri);
		if (sipStack.getLogWriter().isLoggingEnabled()) {
			sipStack.getLogWriter().logDebug("post: fixStrictRouting" + req);
		}
	}

	/**
	 * Utility method to create a hop from a SIP URI
	 * 
	 * @param sipUri
	 * @return
	 */
	

	private final Hop createHop(SipURI sipUri, Request request) {
		// always use TLS when secure
		String transport = sipUri.isSecure() ? SIPConstants.TLS : sipUri
				.getTransportParam();
		if (transport == null) {
			//@see issue 131
			ViaHeader via = (ViaHeader) request.getHeader(ViaHeader.NAME);
			transport = via.getTransport();
		} 
		
		// sipUri.removeParameter("transport");

		int port;
		if (sipUri.getPort() != -1) {
			port = sipUri.getPort();
		} else {
			if (transport.equalsIgnoreCase(SIPConstants.TLS))
				port = 5061;
			else
				port = 5060; // TCP or UDP
		}
		String host = sipUri.getMAddrParam() != null ? sipUri.getMAddrParam()
				: sipUri.getHost();
		AddressResolver addressResolver = this.sipStack.getAddressResolver();
		return addressResolver
				.resolveAddress(new HopImpl(host, port, transport));

	}

	/**
	 * Get the default hop.
	 * 
	 * @return defaultRoute is the default route. public java.util.Iterator
	 *         getDefaultRoute(Request request) { return
	 *         this.getNextHops((SIPRequest)request); }
	 */

	public javax.sip.address.Hop getOutboundProxy() {
		return this.defaultRoute;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.sip.address.Router#getNextHop(javax.sip.message.Request)
	 */
	public ListIterator getNextHops(Request request) {
		try {
			LinkedList llist = new LinkedList();
			llist.add(this.getNextHop(request));
			return llist.listIterator();
		} catch (SipException ex) {
			return null;
		}

	}
}